
#include "include/print2file.h"

#include "boost/filesystem.hpp"
#include "include/kNN.h"
#include <iostream>
#include <fstream>
#include <cmath>
#include <algorithm>
using namespace std;
namespace fs = boost::filesystem;

#define ROOTPROC 0

kNN::kNN(string strDirName,  string outFileName) {

    //ppcustomermovie customermovie;
    if (fs::is_directory(strDirName)) {
        int nCount = 0;
        for (fs::directory_iterator itr(strDirName); itr != fs::directory_iterator(); ++itr) {
            string strFileName = strDirName + (*itr).leaf();
            cout<<strFileName<<endl;
            ppread preprocess(strFileName);
            customermovie.populate(preprocess);
            nCount++;
            cout << "Done with file " << strFileName << " File number " << nCount << endl;
        }
        customermovie.calculateSD();
    }

    outfile.open(outFileName);

}

void kNN::predictnTestK(int K, vector<int> train, vector<int> test, short nMovieID, int numprocs, int ID) {
    // For every customer in test.
    //  find nearest K in train.
    //   then for every movie in vecMovieIDs.
    //    predict the score for the curent customer.
    //    Check the prediction with the real score
    //    record if the prediction is off or not.
    int iterationCount=0;
    for (vector<int>::iterator itrCustID = test.begin();
            itrCustID != test.end();
            itrCustID++, iterationCount++) {
#ifdef USE_MPI
        if (iterationCount % numprocs == ID) {
#endif
            map<int, double> mpCustDist;
            for (vector<int>::iterator itrCustID2 = train.begin();
                    itrCustID2 != train.end();
                    itrCustID2++) {
                if ((*itrCustID2) == (*itrCustID)) {
                    continue;
                }
                double dist = customermovie.findDistance((*itrCustID), (*itrCustID2));
                if (dist != -1.0) {
                    mpCustDist[(*itrCustID2)] = dist;
                }
            }

            vector<int> nearestK = customermovie.findNearestK(mpCustDist, K);

            if (nearestK.size() < 1) {
                continue;
            }
            cout<<"For Customer ID "<<(*itrCustID)<<endl;
            map <int, short> mpMovieRating;
            int nSumrating = 0;

            for (vector<int>::iterator itrNearest = nearestK.begin();
                    itrNearest != nearestK.end();
                    itrNearest++) {
                nSumrating += customermovie.getRating((*itrNearest), nMovieID);
            }

            short predictRating = nearbyint((double) nSumrating / nearestK.size());
            mpMovieRating[2] = predictRating;

            //cout<<"For Movie "<<nMovieID<<" rating is "<<predictRating<<endl;
            outfile.printprediction(*itrCustID, nMovieID, predictRating, customermovie.getRating(*itrCustID, nMovieID));

            custPredictTest[(*itrCustID)] = mpMovieRating;

#ifdef USE_MPI
        }
#endif
    }
    //}
}

void kNN::kFoldXValidation(int k, vector<short> vecMovieIDs, int K, int numprocs, int ID) {//This may be k is different from K of K-NN.
    //For every movie in the vecMovieIDs
        //Check people who saw this movie and get their vector.
    //Get the customer IDs.
    //Get the size of that vector.
    //Devide size by k to get width of fold.
    //create a vector of fold upper boundaries.
    //create a vector of fold lower boundaries.
    //==They together define "k" intervals.
    //For i from 0 to k-1
    //  Create the train vector so that it does not contain any ID
    //  from ith interval.
    //  Create the test vector so that it contains only the train vector.
    //  Call predictnTestK().
    int nMovieCnt = 0;

    for (vector<short>::iterator itrMovieID = vecMovieIDs.begin();
        itrMovieID != vecMovieIDs.end();
        itrMovieID++) {
        nMovieCnt++;
        cout<<"Done "<<nMovieCnt<<" of "<<vecMovieIDs.size()<<" movies."<<endl;

        vector<int> vecCustIDs = customermovie.getCustWhoSawMovie((*itrMovieID));
        //vector<int> vecCustIDs = customermovie.getCustIDs();
        int nvSize = vecCustIDs.size();
        int nfoldWidth = floor((double) nvSize / (double) k);

        vector<int> vecUpperLimits;
        vector<int> vecLowerLimits;
        for (int i = 0; i < k; i++) {
            vecUpperLimits.push_back((i + 1) * nfoldWidth);
            vecLowerLimits.push_back(((i) * nfoldWidth));
        }

#ifdef USE_MPI
        if(ID == ROOTPROC){
#endif
        char buff[100];
        sprintf(buff,"Movie_%d_CustIDs.txt", (*itrMovieID));
        string printFile(buff);
        print2file movieOutfile(printFile);
        movieOutfile.printvector(vecCustIDs);

#ifdef USE_MPI
        }
#endif
        srand(500);
        random_shuffle(vecCustIDs.begin(), vecCustIDs.end());
        for (int i = 0; i < k; i++) {
            int validateUB = vecUpperLimits.at(i);
            int validateLB = vecLowerLimits.at(i);
            vector<int> vecTrain;
            vector<int> vecTest;
            for(int idx =0; idx < vecCustIDs.size(); idx++){
                if(idx < validateUB && idx >= validateLB){
                    vecTest.push_back(vecCustIDs[idx]);
                }else{
                    vecTrain.push_back(vecCustIDs[idx]);
                }
            }
            predictnTestK(K, vecTrain, vecTest, (*itrMovieID), numprocs, ID);
        }
    }
}


void kNN::findNearest(int numprocs, int rank, int K) {
    vector<int> vecCustID = customermovie.getCustIDs();

    map<int, map<int, double> > custCustDistance;
#ifdef USE_MPI
    cout << "The size of the vector is " << vecCustID.size() << " for rank" << rank << endl;
    char buff[100];
    sprintf(buff, "%d", rank);
    string strOpFileName = buff + string("_out.txt");
    fstream pout(strOpFileName.c_str(), ios::out);
    if (!pout.is_open()) {
        cout << "too bad cannot open file " << strOpFileName << endl;
        exit(1);
    }
#else
    char buff[100];
    sprintf(buff, "%d", rank);
    string strOpFileName = buff + string("_out.txt");
    fstream pout(strOpFileName.c_str(), ios::out);
    if (!pout.is_open()) {
        cout << "too bad cannot open file " << strOpFileName << endl;
        exit(1);
    }
#endif
    for (vector<int>::iterator itrCustID = vecCustID.begin();
            itrCustID != vecCustID.end();
            itrCustID++) {
#ifdef USE_MPI
        if (*itrCustID % numprocs == rank) {
#endif
            map<int, double> mpCustDist;
            for (vector<int>::iterator itrCustID2 = vecCustID.begin();
                    itrCustID2 != vecCustID.end();
                    itrCustID2++) {
                if ((*itrCustID2) == (*itrCustID)) {
                    continue;
                }
                double dist = customermovie.findDistance((*itrCustID), (*itrCustID2));
                if (dist != -1.0) {
                    mpCustDist[(*itrCustID2)] = dist;
                    //cout<<"The ID1 is "<<(*itrCustID)<<" ID2 is "<<(*itrCustID2)<<"distance is"<<dist<<endl;
                }
                //cout<<"The ID1 is "<<(*itrCustID)<<" ID2 is "<<(*itrCustID2)<<"distance is"<<dist<<endl;
            }

            vector<int> nearestK = customermovie.findNearestK(mpCustDist, K);
#ifdef USE_MPI
            pout << "For customer with CustID " << (*itrCustID) << "for Rank" << rank << endl;
#else
            pout << "For customer with CustID " << (*itrCustID) << endl;
#endif
            if (nearestK.size() > 0) {
                for (int i = 0; i < K || i < nearestK.size(); i++) {
#ifdef USE_MPI
                    pout << " the close # " << i << "is " << nearestK[i] << endl;
#else
                    pout << " the close # " << i << "is " << nearestK[i] << endl;
#endif
                }
            }
#ifdef USE_MPI        
        }
#endif     

        //custCustDistance[(*itrCustID)] = mpCustDist;
    }
    pout.close();
}

void kNN::printPrediction(){
    
    for(map<int, map<int, short> >::iterator itrResult = custPredictTest.begin();
        itrResult != custPredictTest.end();
        itrResult++){
        cout<<"For the Customer with ID "<<(*itrResult).first<<endl;
        for(map<int, short>::iterator itrMovieRating = (*itrResult).second.begin();
            itrMovieRating != (*itrResult).second.end();
            itrMovieRating++){
            cout<<"     Prediction for movie ID "<<(*itrMovieRating).first<<" is "<<(*itrMovieRating).second;
            cout<<" the original rating is "<<customermovie.getRating((*itrResult).first, (*itrMovieRating).first)<<endl;
        }
    }
}
